﻿using System;
using ArithmeticLibrary;

namespace Nolineq.Methods
{
    /// <summary>
    /// Resic rovnic Newtnovou metodou.
    /// </summary>
    public class NewtonsMethod : EquationSolver
    {
        #region Constructors

        /// <summary>
        /// Vytvori instanci resice rovnice metodou tecen.
        /// </summary>
        /// <param name="function"></param>
        public NewtonsMethod(Function function, double start, double end, double root, double precision, int maxSteps)
            : base(function, start, end, root, precision, maxSteps)
        {
            this.Name = "Newtonova metoda";
        }

        #endregion // Constructors
        
        /// <summary>
        /// Vyresi rovnici metodou tecen.
        /// </summary>
        /// <param name="start">Zacatek intervalu.</param>
        /// <param name="kon">Konec intervalu.</param>
        /// <param name="precision">Presnost.</param>
        /// <returns>Vrati koren rovnice.</returns>
        public override Solution Solve()
        {
            // PREVENCE proti zamrznuti programu. Pocita pocet kroku pri vypoctu.
            int steps = 0;

            // Pocatecni hodnoty
            double oldResult = Root;
            double oldStep = 1;
            double newResult = 0;

            bool pokracovat = true;
            bool firstStep = true;

            // jestli je nahodou koren v krajnim bode intervalu
            if (Function.Evaluate(Start) == 0)
                return new Solution(Start, 0);

            if (Function.Evaluate(End) == 0)
                return new Solution(End, 0);
            
            // pokud jsme se trefili primo do korene, tak vratime koren
            if (Function.Evaluate(oldResult) == 0)
                return new Solution(oldResult, 0);

            // Dokud absolutni hodnota funkcni hodnoty stareho korene presahuji presnost.
            while(Math.Abs(Function.Evaluate(oldResult)) >= Precision && pokracovat && steps < MaxSteps)
            {
                // prvni derivace v bode nesmi byt nulova.
                if (Function.Evaluate1stDerivation(oldResult) == 0)
                    throw new UnsuitableMethodException("f'(" + oldResult + ") = 0 - hodnota první derivace v bodě je nulová. " +
                        "Zkuste upravit počáteční kořen.");
                
                // Vypocet noveho korene
                newResult = (-Function.Evaluate(oldResult) + (Function.Evaluate1stDerivation(oldResult) * oldResult)) / Function.Evaluate1stDerivation(oldResult);
                double newStep = Math.Abs(newResult - oldResult);

                // Pokud je to byl prvni nove vypocteny koren.
                if (firstStep)
                {
                    oldResult = newResult;
                    oldStep = newStep;
                    firstStep = false;
                } 
                else
                {
                    // Zmenseni intervalu podle noveho korene.
                    if (newStep < oldStep)
                    {
                        oldResult = newResult;
                        oldStep = newStep;
                    }
                    else if (newStep > oldStep)
                    {
                        pokracovat = false;
                        throw new UnsuitableMethodException("Metoda nemohla nalézt za daných vstupních parametrů žádný kořen. Zkuste upravit počáteční kořen nebo zvolte jinou metodu.");
                    }
                }
                
                ++steps;
            }

            // kontrola prekroceni limitu iteraci
            if (steps == MaxSteps)
                throw new UnsuitableMethodException("Překročen limit počtu iterací. Zkuste nastavit větší počet iterací, upravit počáteční parametry nebo zvolte jinou metodu.");
                
            return new Solution(newResult, steps);
        }

    }
}
